基本的にはgithubに すべてが書かれている. リファレンスはここから 確認すること. 探すと色々と関数があるのはわかるけど,いまいちjoinについて 記法がわからない. いわゆるleft_joinはあるのだが,inner, outerなどがわかればより 有り難いのだが・・・・. overlapjoinやnon-equijoinなどがある. by = .EACHIはどのような意味なのだろうか? each i を示す特別な記号であるのはわかったが.
target_libs <- c(
"data.table",
"fs",
"purrr",
"ggplot2"
)
for (lib in target_libs) {
if (!require(lib, character.only = TRUE)) {
try({install.packages(lib); library(lib, character.only = TRUE)})
}
}
# constant
DATA_DIR <- "D"ここ のチュートリアルをやる.
データの読み込みはfread関数を使う. freadは、http/httpsをインプットファイルとして指定することも可能である.
## [1] "data.table" "data.frame"
data.tableはdata.frameを強化したものを与えるRのパッケージである. インスタンスの作成はfreadによるものあるし,下記のようにdata.table関数で 作成することが可能である.
## [1] "data.table" "data.frame"
ほかにも,setDT,as.data.tableから作成することができる. 前者はdata.frameやlistの構造を対象としたもので,後者はそれ以外を対象としたものである.
# 既存のオブジェクトを変換する際には, copyしておく必要がある.
iris_dt <- copy(iris)
print(list(address(iris), address(iris_dt)))## [[1]]
## [1] "00000000261cb3b0"
##
## [[2]]
## [1] "0000000020738e68"
## [1] "data.table" "data.frame"
ところで,data.frameに対して何が強化されているのか. data.frameに対して,行や列の選択ができるようになっていること 以外にも様々なことが[ ... ]のように角括弧を通じて行える.
generalシンタックスは次である. i, j, byがそれぞれSQLと対応しているとうイメージになる.
DT[i, j, by]
R: i j by
SQL: where | order by select | update group by
さっそく実践してみる.
# j,byが必要ない場合にはカンマを省略することができる
# カンマをつけていても問題はない
ans <- flights[origin == "JFK" & month == 6L]
head(ans)ソートをしてみる. この例ではoriginを昇順により並び変えた上で, destを降順で並び変える. このとき使われているorderはベースRのものである.
さて,列を選択してみる. 単純にはベクトルで返される.
## [1] 13 13 9 -26 1 0
data.table, あるいは複数列を選択する場合には, listを使うこと.
data.tableではlistの代わりとして.()を使うことができる. .()はlistのエイリアスである.
## [1] TRUE
.()がlistのエイリアスであることがわかっていれば, 次のように列名の変更が有効であることがわかる.
さてjを使い計算をしてみる.
## [1] 141814
なにが起こったのだろうか.
実はjというのは列を選択するだけでなく, 列を使った表現式を扱うことが可能なのである.
6月のJFK空港を基点としたすべての到着と発着における送れについて,平均値を 計算してみる.
ans <- flights[origin == "JFK" & month == 6L,
.(m_arr = mean(arr_delay), m_dep = mean(dep_delay))]
ansカウントについてもお手の物である.
スペシャルシンボルの.Nを使うこともできる.
## NULL
## [1] 8422
上述した操作は次の操作と同値である. しかし,次の操作は,1度全体のコピーを作成して,そこに関数を適用していることから, 上述した操作と比べて非効率である.
nrow(flights[origin == "JFK" & month == 6L])
文字列としてのカラム名を使い参照することも可能である.
変数としてカラムの文字列を有している時には次の2つの使い方がある.
他にもカラム選択のやり方として,非選択を選択することも可能である.
ここから,ここまで,というやり方でも列を選択することが可能である.
byがどのようにグルーピングとして機能するのかを見ていく. ここまでをちゃんと読んでいれば次の操作で起こっていることは明らかである. もちろん変数は1つなので,.()を使う必要はない.
配列部分で複数の変数をしているできるのは, by引数においても同様.
byに文字列ベクトルを使うことも可能である.
aggregationは複数の変数に対しても行える.
ans <- flights[
carrier == "AA",
.(ad_mean = mean(arr_delay), dd_mean = mean(dep_delay)),
by = .(origin, dest, month)
]
head(ans)ところで,上記の結果において,origi, dest, monthの順にオーダーをつけたい場合には どのようにすれば良いのか. 次の操作のようにkeybyを使う. 昇順でしか使えないのかな?
ans <- flights[
carrier == "AA",
.(ad_mean = mean(arr_delay), dd_mean = mean(dep_delay)),
keyby = .(origin, dest, month)
]
head(ans)DTは処理を,つまり[ ]をつなげていくことができる. これにより,結果をソートなども中間変数を用意せずに行うことが可能である.
i, jと同様にbyは表現式をハンドリングできる. これを見ると列名を直接参照しなくてもよい,つまり, いわゆるmutateのシンタックスが使われていると考えればよい.
ところで,平均値をすべての列に計算したいとする. もちろんすべての変数に対してmean(column)を記述するのは実践的ではない.
data.tableパッケージでは,スペシャルシンボルの.SDを提供している. .SDは「subset of data」であり, byを使い定義した現在のカレントグループのデータそのものを参照する.
## a b c
## 1: 1 7 13
## 2: 2 8 14
## 3: 3 9 15
## a b c
## 1: 4 10 16
## 2: 5 11 17
## a b c
## 1: 6 12 18
上記のように, .SDがデータのサブセットを参照しているので, これに対してlapplyを使うことができる. つまり, サブデータセットのカラムごとに演算が可能となる. data.tableがdata.tableでもあることの利点がわかった気がする.
ポイントとしてはlapplyが返すのはlistのため, .()でラップする必要はないということ.
.SDcols引数では,カラムネームかカラムのインデックスを受け付けることができる. .SDcolsで指定したカラムだけがSDに含まれてることを補償することができる. カラムの指定は非選択のシンタックスや,colA:colBといったことも可能である.
flights[
carrier == "AA",
lapply(.SD, mean),
by = .(origin, dest, month),
.SDcols = c("arr_delay", "dep_delay")
][1:10]最初の2行だけ抽出するということも可能である.
カラムa, bをIDグループごとに結合するには?
a, b列のすべての値を結合した値を持つがリストとして返すには? サマラズしているということだね!
add/update/deleteに関する辞書,とi, byとの連携について示す.
reference semanticeに関するブリーフな議論と,:=オペレータを 使うことができる2つの形を見るなどをしていく.
data.frameで作成したデータに対して,次の2つの操作は どちらもすべてのデータがコピーされる.
## [1] "000000001ad76828"
## [1] "000000001a04b2a8"
## [1] "000000001a04b468"
## [1] "000000001a7773c0"
## [1] "000000001a04b2a8"
## [1] "0000000018649550"
## [1] "000000001a7f9a00"
## [1] "000000001a04b2a8"
## [1] "000000001a7f9af0"
というが,バージョン4では事情が異なるよう. 上の結果を下記にまとめる.
これはどうなのだろうか?結局のところポインターが変わるのがつまり, データをディープコピーしているわけではなさそう.
ところで,:=オペレータは上記の2つの操作でどちらもインプレイスで処理するため ディープコピーを作成しない. らしいが,下記の結果をみると一応, 列を変更するとその列のアドレスは変更されているのだが・・・?
アドレスが変わる操作と変わらない操作があるようです.
## [1] "000000001a385828"
## [1] "000000001a385828"
## [1] "0000000019cf0eb0"
複数行の処理が行える.
##
## Attaching package: 'purrr'
## The following object is masked from 'package:data.table':
##
## transpose
## Warning in `[.data.table`(DT, , `:=`(b = nrm_v[[1]])): 0.196929 (type
## 'double') at RHS position 1 truncated (precision lost) when assigning to type
## 'integer' (column 3 named 'b')
ということでえ本編に戻る.
## [1] 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
24時を0時に直す
## [1] 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
複数の列を掃除に操作するやり方.
in_cols = c("dep_delay", "arr_delay")
ot_cols = c("max_dep_delay", "max_arr_delay")
# with = FALSEにするか,c(ot_cols)にしないとそれが列名として解釈されることに注意な
# ..ot_colsも効果がなかった
flights[, ot_cols := lapply(.SD, min), by = month, .SDcols = in_cols, with = FALSE][]## Warning in `[.data.table`(flights, , `:=`(ot_cols, lapply(.SD, min)), by =
## month, : with=FALSE together with := was deprecated in v1.9.4 released Oct 2014.
## Please wrap the LHS of := with parentheses; e.g., DT[,(myVar):=sum(b),by=a] to
## assign to column name(s) held in variable myVar. See ?':=' for other examples.
## As warned in 2014, this is now a warning.
:=は参照系なので副作用がある.
foo <- function(DT) {
DT[, speed := distance / (air_time/60)]
DT[, .(max_speed = max(speed)), by = month]
}
ans = foo(flights)
head(flights[, "speed"]) # speedがある!参照系で処理しないようにするにはcopy関数を使うこと.
flights[, speed := NULL]
foo <- function(DT) {
DT <- copy(DT) ## deep copy
DT[, speed := distance / (air_time/60)] ## doesn't affect 'flights'
DT[, .(max_speed = max(speed)), by = month]
}
ans <- foo(flights)
names(flights)## [1] "year" "month" "day" "dep_delay"
## [5] "arr_delay" "carrier" "origin" "dest"
## [9] "air_time" "distance" "hour" "delay"
## [13] "max_dep_delay" "max_arr_delay"
コピーにより副作用なしで演算が出来ていることがわかる. ということだが,いまのバージョンでは上記をシャローコピーで 実現ができるらしい.
DT <- data.table(x = 1L, y = 2L)
DT_n <- names(DT)
# add new column
DT[, z := 3L]
# DT_n にも追加されている
DT_n## [1] "x" "y" "z"
## [1] "x" "y" "z"
ところでggplot2は使えるのか? つかえました.
このチュートリアルの目的は次です.
ここまでは, iにおいてデータのサブセットを抽出するとき, 論理式,行番号,orderを使ったものを紹介してきた. ここでは,keyを使った高速な処理を紹介する.
とりあえず,データフレームをつくってみる.
set.seed(1L)
DF <- data.frame(
ID1 = sample(letters[1:2], 10, TRUE),
ID2 = sample(1:3, 10, TRUE),
val = sample(10),
stringsAsFactors = FALSE,
row.names = sample(LETTERS[1:10])
)
DF## [1] "I" "D" "G" "A" "B" "E" "C" "J" "F" "H"
data.frameにおいてもrownameを使いデータのサブセットを作成する ことが可能である.
上記の例からrownameというのは,行番号へのインデックスの 役割があることがわかる. ただし,ユニークである必要がある.
ここで,data.tableに変換してみる.
## [1] "1" "2" "3" "4" "5" "6" "7" "8" "9" "10"
data.tableに変換することでrow nameはリセットされていることがわかる. data.tableは決してrow nameは使わない. data.tableはdata.frameを継承しているため, row name属性を有しているが,使わない.この理由は後でみられる. もしrow nameを保持したいときにはkeep.rownames = TRUEという 引数を使うこと.
data.tableではkeysを設定,利用する. keysは超高速なrownamesと考えれば良い. keysの特徴は次である.
一般的にはkeyは1つのカラムに対して設定する.
setkeyvを使うと,文字列ベクトルを使いkeyを設定することが可能keyが設定されたdata.tableでは, .()をiで使い, サブセットを作成することが可能となる.
## [1] "origin"
## [1] "origin" "dest"
2つのkeyを設定したのでそれぞれを指定したサブセットが作成可能.
上記の処理はまず“JFK”をoriginから探し, その後に“MIA”をdestから探索している.
2つ目のkeyだけを使いサブセットを作るには1つ工夫が必要となる.
## [1] "origin" "dest"
もちろんjにおける演算と組み合わせることができる.
## [1] 486
## [1] 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
すでにやったexampleであるが,keyによる参照においても次のように サブセットに対する処理が可能.
## [1] "hour"
## NULL
上記の処理ではkeyに使ったカラムhourを修正した. これによりhourの昇順の整列がくずれた. よって,keyはNULLを設定するという方法を使い, 自動で取り除かれている.
## [1] 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
集計処理についても同様にkeyを駆使して処理することが可能である.
## [1] "origin" "dest"
## [1] "month"
任意のクエリーにおいて,すべての行を返すのか, 最初あるいは最後の行を返すのかを指定することができる.
ところで,keyのメリットはなんなのだろうか.
# このようなサブセットの作成はベクトルスキャンでも可能である
identical(
flights[.("JFK", "MIA")],
flights[origin == "JFK" & dest == "MIA"]
)## [1] TRUE
記法が短いという意味合いもあるが, 端的にいって高速であることがアドバンテージである.
set.seed(2L)
N = 2e7L
DT = data.table(x = sample(letters, N, TRUE),
y = sample(1000L, N, TRUE),
val = runif(N))
print(object.size(DT), units = "Mb")## 381.5 Mb
## user system elapsed
## 0.75 0.08 0.29
## [1] 762 3
## [1] "x" "y"
## user system elapsed
## 0 0 0
## [1] 762 3
## [1] TRUE
このように高速になるのは, データがソート済みになっていることで バイナリーサーチが使えるためである. 通常のベクトルスキャンでは毎回すべての要素との比較が行われており, なんどもサブセットを作成する情況においては非効率である.
## [1] 253316 11
onを使っておこなう
第二インデックスとは,keyと似たものであるが次の点が異なる
## [1] "names" "row.names" "class"
## [4] ".internal.selfref" "index"
setindex(flights, NULL)によりインデクスは削除## [1] "origin"
## [1] "origin" "origin__dest"
ところでなぜセカンダリーインデックスが必要であるのか?
1つにはデータを物理的に並び変えるのはコストが大きい場合があり, 常に理想的な処理であるとは言えない. たとえば参照するカラムとkeyが異なる場合, そのたびにリオーダーが発生する. このような場合にセカンダリーが有効になる.
セカンダリーは,複数のインデックスを,属性として保持することが できるのでリオーダーの時間を節約することができる. on引数は自動でセカンダリーを作成する.
上記の処理でオンザフライでインデックスが作成されている. 処理速度はバイナリーサーチを用いた場合と同様である. ただし,インデックスは自動では保存されていない. これについては今後変更される予定である.
もし既にセカンダリーをsetindexで作成しているときには, onはセカンダリーを再利用する. このことはverbose= TRUEによりみることができる.
## i.V1 has same type (character) as x.origin. No coercion needed.
## on= matches existing index, using index
## Starting bmerge ...
## forder.c received 1 rows and 1 columns
## bmerge done in 0.000s elapsed (0.000s cpu)
## Constructing irows for '!byjoin || nqbyjoin' ... 0.000s elapsed (0.000s cpu)
カラムが複数あるときにはベクトルで指定すればよい.
jとの組み合わせは既にみてきたkeyがある場合の 組み合わせと同じである. 属性にindexが与えられることだけに注意する.
チャイニングも同じである.
サマライズも同じ.
## [1] 486
サブアサインも同じ.
## [1] 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
## [1] 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
集計も同じ. iにインデックス記法を使わない場合には, onは必要ない. keybyはkeyを与えているのではなくて,順序化したグループ化の変数で, 自動でkeyが与えられる. ちなみにoriginカラムはなくなるのでインデックスは付いていない...
## [1] "month"
## NULL
multも正しく動く.
nomatchも動く.
set.seed(1L)
dt = data.table(x = sample(1e5L, 1e7L, TRUE), y = runif(100L))
print(object.size(dt), units = "Mb")## 114.4 Mb
## [1] "names" "row.names" "class"
## [4] ".internal.selfref"
## user system elapsed
## 1.05 0.09 0.36
## [1] "names" "row.names" "class"
## [4] ".internal.selfref" "index"
## [1] "x"
1度目の参照時間はインデックスを作成する時間と, サブセットを抽出する時間の和になっている. 自動インデックスはoptions(datatable.auto.index = FALSE)により 停止することも出来るが,実際にはベクタースキャンよりも 2回目以降は下に示すようにより高速に行える. 現在のところ自動インデックスは==と%in%の 場合に設定される.
## user system elapsed
## 0 0 0
## user system elapsed
## 0.01 0.00 0.02
## NULL
meltとdcastはdata.tableのための縦横変換関数であり, 10GBを超えるin memoryのデータを想定sちえ実装されていr.
s1 <- "family_id age_mother dob_child1 dob_child2 dob_child3
1 30 1998-11-26 2000-01-29 NA
2 27 1996-06-22 NA NA
3 26 2002-07-11 2004-04-05 2007-09-02
4 32 2004-10-10 2009-08-27 2012-07-21
5 29 2000-12-05 2005-02-28 NA"
DT <- fread(s1)
DTmeltを使い再構築する.
DT.m1 <- melt(
DT,
# IDとして使う変数(age_motherはいつ年齢?)
id.vars = c("family_id", "age_mother"),
# 観測値の値として使う変数
measure.vars = c("dob_child1", "dob_child2", "dob_child3")
)
DT.m1meltと同じに名前を与える.
DT.m1 <- melt(
DT,
# IDとして使う変数(age_motherはいつ年齢?)
id.vars = c("family_id", "age_mother"),
# 観測値の値として使う変数
measure.vars = c("dob_child1", "dob_child2", "dob_child3"),
# 観測値の変数名
variable.name = "child",
# 観測値の値の変数名
value.name = "dob"
)
DT.m1簡単に記述できない場合を考えて見る.
s2 <- "family_id age_mother dob_child1 dob_child2 dob_child3 gender_child1 gender_child2 gender_child3
1 30 1998-11-26 2000-01-29 NA 1 2 NA
2 27 1996-06-22 NA NA 2 NA NA
3 26 2002-07-11 2004-04-05 2007-09-02 2 2 1
4 32 2004-10-10 2009-08-27 2012-07-21 1 1 1
5 29 2000-12-05 2005-02-28 NA 2 1 NA"
DT <- fread(s2)
DTdobとgenderを観測値の変数としてまとめることを考える. いまある機能を使うと次のように, 1度全体を融かして,変数部分だけ分離させるという方法で記述ができる.
DT.m1 <- melt(DT, id = c("family_id", "age_mother"))
DT.m1[, c("variable", "child") := tstrsplit(variable, "_", fixed = TRUE)]
DT.m1[1:5]しかし,この方法には次の問題点がある.
stats::reshapeこの問題を簡単に解決してくれる. (自分で試してみるべし!とのこと)
カラムのリストをmeasure.varsに渡すことで解決する.
colA = paste("dob_child", 1:3, sep = "")
colB = paste("gender_child", 1:3, sep = "")
DT.m2 = melt(DT,
measure = list(colA, colB),
value.name = c("dob", "gender"))
DT.m2すべてを指定することが難しい場合にはpatternsを使うこと.
DT.m2 <-
melt(
DT,
# measureでもいいみたいだが,上との対応のため,measure.varsを使う
# 本当は用途が異なる可能性が多分にある・・・
measure.vars = patterns ("^dob", "^gender"),
value.name = c("dob", "gender")
)
DT.m2cheet sheetを一通りなめる.
文字だけの場合にはどちらでも良い.
文字列を値とした変数を使う場合はやり方が二通りある.
inplaceで変更する.
複数列に対して演算するときには少し複雑.
byは,.SDに適用した結果をbind_rowsするイメージ
プリントすると,.SDがそれぞれdata.tableであることが わかる.
## a b
## 1: 10 6+0i
## 2: 20 7+0i
## a b
## 1: 30 8+0i
## a b
## 1: 40 49+0i
## 2: 50 60+0i
集約処理も簡単に行える.
ユニークなカラム数を見極める.
## [1] 5
dt1 <- data.table(
a = 1:3,
b = c("a", "b", "c")
)
dt2 <- data.table(
x = 3:1,
y = c("b", "c", "a")
)dt_a <- copy(dt1)[, c := c(7, 5, 6)]
dt_b <- copy(dt2)[, z := c(4, 5, 8)]
dt_a[dt_b, on = .(b = y, c > z)]dt_a <-
data.table(
a = c(1, 2, 3, 1, 2),
id = c("A", "A", "A", "B", "B"),
date = seq(as.Date("2020/1/1"), by = "3 days", length.out = 5)
)
dt_adt_b <-
data.table(
b = c(1, 1),
id = c("A", "B"),
date = seq(as.Date("2020/1/2"), by = "2 days", length.out = 2)
)
dt_b通常のジョインだとマッチはしない.
ローリングだと最も近い直前の値でマッチすることが出来る. マッチしないとNAになっていることがわかる.
dt <-
data.table(
id = c("A", "A", "B", "B"),
y = c("x", "z", "x", "z"),
a = c(1, 2, 1, 2),
b = c(3, 4, 3, 4)
)
dtdt_long <-
melt(
dt_wide,
id.vars = c("id"),
measure.vars = patterns("^a"),
variable.name = "y",
value.name = c("a")
)
dt_long複数行に対してもできるが,variableが因子になるのに注意.
melt(
dt_wide,
id.vars = c("id"),
measure.vars = patterns("^a", "^b"),
variable.name = "y",
value.name = c("a", "b")
)ここの Examplesから, ここまでであまり理解していなかった記述方法について試す.
DF = data.frame(x=rep(c("b","a","c"),each=3), y=c(1,3,6), v=1:9)
DT = data.table(x=rep(c("b","a","c"),each=3), y=c(1,3,6), v=1:9)
DF## NAME NROW NCOL MB
## 1: ans 101 2 0
## 2: ans1 762 3 0
## 3: ans2 762 3 0
## 4: d 26 3 0
## 5: dt 4 4 0
## 6: DT 9 3 0
## 7: DT.c1 15 5 0
## 8: DT.c2 5 8 0
## 9: DT.m1 30 5 0
## 10: DT.m2 15 5 0
## 11: dt_a 5 3 0
## 12: dt_b 2 3 0
## 13: dt_long 4 3 0
## 14: dt_wide 2 5 0
## 15: dt1 3 2 0
## 16: dt2 3 2 0
## 17: flights 253,316 11 14
## 18: iris_dt 150 5 0
## COLS
## 1: x,y
## 2: x,y,val
## 3: x,y,val
## 4: a,b,c
## 5: id,y,a,b
## 6: x,y,v
## 7: family_id,age_mother,child,dob,gender
## 8: family_id,age_mother,dob_1,dob_2,dob_3,gender_1,...
## 9: family_id,age_mother,variable,value,child
## 10: family_id,age_mother,variable,dob,gender
## 11: a,id,date
## 12: b,id,date
## 13: id,y,a
## 14: id,a_x,a_z,b_x,b_z
## 15: a,b
## 16: x,y
## 17: year,month,day,dep_delay,arr_delay,carrier,...
## 18: Sepal.Length,Sepal.Width,Petal.Length,Petal.Width,Species
## KEY
## 1:
## 2:
## 3: x,y
## 4:
## 5:
## 6:
## 7: family_id,age_mother,child
## 8: family_id,age_mother
## 9:
## 10:
## 11:
## 12:
## 13:
## 14: id
## 15:
## 16:
## 17:
## 18:
## Total: 14MB
## [1] 1 2 3 4 5 6 7 8 9
## NULL
.EACHIの便利なところかな? よくわからないけど,on と同じ変数でbyされるということな.
kDT = copy(DT) # (deep) copy DT to kDT to work with it.
setkey(kDT,x) # set a 1-column key. No quotes, for convenience.
setkeyv(kDT,"x") # same (v in setkeyv stands for vector)
v="x"
setkeyv(kDT,v) # same
# key(kDT)<-"x" # copies whole table, please use set* functions instead
haskey(kDT) # TRUE## [1] TRUE
## [1] "x"
## NULL
## NULL
## [1] 1 2 3 4 5 6 7 8 9
## list(x = "b", y = 1)
## list(x = "b", y = 3)
## list(x = "b", y = 6)
## list(x = "a", y = 1)
## list(x = "a", y = 3)
## list(x = "a", y = 6)
## list(x = "c", y = 1)
## list(x = "c", y = 3)
## list(x = "c", y = 6)
これはなにをしているのかがわかっていない・・
DT[, {
# write each group to a different file
fwrite(.SD, file.path(tempdir(), paste0('x=', .BY$x, " y = ", .BY$y, '.csv')))
}, by=.(x, y)]## [1] "file3afc11562517" "file3afc152d5dfe" "file3afc18744d99" "file3afc1cfd2dee"
## [5] "file3afc1e1b6dae" "file3afc1f197455" "file3afc1f525c2" "file3afc1f5367b7"
## [9] "file3afc208e40fe" "file3afc21011718" "file3afc22d734df" "file3afc241f1620"
## [13] "file3afc252060d2" "file3afc2914785f" "file3afc304734e1" "file3afc3389ef4"
## [17] "file3afc350f1cf2" "file3afc36581d89" "file3afc3b7c418f" "file3afc3bc10de"
## [21] "file3afc3bc6353" "file3afc3c565afa" "file3afc3f12e68" "file3afc42fbb02"
## [25] "file3afc442b6074" "file3afc45aa30ac" "file3afc46055ae3" "file3afc4a5d2259"
## [29] "file3afc4d1e71cd" "file3afc4d33727" "file3afc4fa160b4" "file3afc54b4463b"
## [33] "file3afc5a202bd6" "file3afc5ae17a72" "file3afc67054d33" "file3afc676e59be"
## [37] "file3afc68d366b0" "file3afc6ada4c9f" "file3afc6cf799d" "file3afc6ec15cb"
## [41] "file3afc70f41049" "file3afc73dfcbe" "file3afc751b7313" "file3afc774732ef"
## [45] "file3afc79974656" "file3afc7b2c4e37" "file3afc7be15d7f" "file3afc7bec6af9"
## [49] "file3afc7e281d63" "file3afc7f804bb2" "file3afc8ab27af" "file3afc8ae7543"
## [53] "file3afca7c7591" "file3afca955126" "file3afcab5d02" "x=a y = 1.csv"
## [57] "x=a y = 3.csv" "x=a y = 6.csv" "x=b y = 1.csv" "x=b y = 3.csv"
## [61] "x=b y = 6.csv" "x=c y = 1.csv" "x=c y = 3.csv" "x=c y = 6.csv"
DT = data.table(
x=rep(c("b","a","c"),each=3),
v=c(1,1,1,2,2,1,1,2,2),
y=c(1,3,6),
a=1:9, b=9:1)
DT[, sum(v), by=.(y%%2)] 一時変数が欲しい場合には,jで波括弧を使えば良い.
# expression. TO REMEMBER: every element of
# the list becomes a column in result.
pdf("new.pdf")
DT[, plot(a,b), by=x] # can also plot in 'j'## png
## 2
## starting httpd help server ... done
## [1] 1 1 2 2 3 3 3 4 5 5
## [1] "grp1" "grp1" "grp2" "grp2" "grp3" "grp3" "grp3" "grp4" "grp5" "grp5"
公式のチュートリアルに出てこない関数たちの調査結果.
引数が切り替わるごとに1から番号振りを始める
## [1] 1 1 2 1 2 2
## [1] 1 1 2 1 2 2
## [1] "group1" "group1" "group2" "group1" "group2" "group2"
## [1] 1 1 2 1 2 1
## [1] 1 1 2 1 2 1
## [1] 1 1 2 1 2 1
集合演算が行えるようです.
x = data.table(c(1,2,2,2,3,4,4))
x2 = data.table(c(1,2,3,4)) # same set of rows as x
y = data.table(c(2,3,4,4,4,5))all=FALSEという引数がある. この場合には,重複した行は削除されれる. all=TRUEのとき,は個別にみるイメージである. 逆を言えば, all=FALSEは本当に集合として処理を行うイメージである.
## [1] TRUE
## [1] FALSE
ぱっと見だけど区間の話をしているようにみえる. xのstart/endの間において,yのstart/endの間がラップしているのかどうかを判定している. あんまり使いどころがわかっていないが, バイナリーサーチで行われるらしいので必要になったら高速に処理が可能である.
x = data.table(start=c(5,31,22,16), end=c(8,50,25,18), val2 = 7:10)
y = data.table(start=c(10, 20, 30), end=c(15, 35, 45), val1 = 1:3)
setkey(y, start, end)
foverlaps(x, y, type="any", which=TRUE)これはオーバーラップのなかでも,包含を対象としている.
## [[1]]
## [[1]]$x
## [1] 1
##
## [[1]]$y
## [1] 6
##
##
## [[2]]
## [[2]]$x
## [1] 2
##
## [[2]]$y
## [1] 7
##
##
## [[3]]
## [[3]]$x
## [1] 3
##
## [[3]]$y
## [1] 8
##
##
## [[4]]
## [[4]]$x
## [1] 4
##
## [[4]]$y
## [1] 9
##
##
## [[5]]
## [[5]]$x
## [1] 5
##
## [[5]]$y
## [1] 10
通常のlengthだと,カラム数を返すのだけど, 実際にはアロケートされている列の数が異なるので, アロケートされている分までを出力する.
## [1] 2
## [1] 1026
## [1] 2
## [1] 2050
traspose(strsplit())と同じ. つまり,は分割した文字列を列ごとに保持するようになる.
# names引数があるのでそれを使うことも良い
a <- data.table(
x = c("x_1", "x_2", "x")
)[, c("x", "n") := tstrsplit(x, "_", fill = "0")]
a[]リストに保持された複数のデータテーブルをひとつにまとめたものを 作成する.
DT1 = data.table(A=1:3,B=letters[1:3])
DT2 = data.table(A=4:5,B=letters[4:5])
l = list(DT1,DT2)
rbindlist(l)カラム名が異なっていても,引数で調整することが可能.
DT1 = data.table(A=1:3,B=letters[1:3])
DT2 = data.table(B=letters[4:5],C=factor(1:2))
l = list(DT1,DT2)
rbindlist(l, use.names=TRUE, fill=TRUE)行と列を指定して,値を設定する関数. 行ごとに値を設定する処理の時に有効にみえる. ただし,基本的に行単位で値を繰り返し設定することは 稀であるので,あまり気にしなくても良い気がする.
bench::mark(
system.time(for (i in 1:1000) DF[i, 1] = i),
system.time(for (i in 1:1000) DT[i, V1 := i]),
system.time(for (i in 1:1000) set(DT, i, 1L, i)),
check = FALSE
)## Warning: Some expressions had a GC in every iteration; so filtering is disabled.
## # A tibble: 3 x 6
## expression min median `itr/sec`
## <bch:expr> <bch:t> <bch:t> <dbl>
## 1 system.time(for (i in 1:1000) DF[i, 1] = i) 4.7s 4.7s 0.213
## 2 system.time(for (i in 1:1000) DT[i, `:=`(V1, i)]) 317ms 393.6ms 2.54
## 3 system.time(for (i in 1:1000) set(DT, i, 1L, i)) 45.9ms 46.9ms 21.2
## # ... with 2 more variables: mem_alloc <bch:byt>, `gc/sec` <dbl>
## [1] NA NA 3 4 4 4 7 8 8 8
## [[1]]
## [1] 3 3 3 4 7 7 7 8 NA NA
##
## [[2]]
## [1] 1.5 1.5 1.5 1.5 2.0 3.5 3.5 3.5 4.0 NA
##
## [[3]]
## [1] 1.5 1.5 2.0 3.5 3.5 3.5 4.0 NA NA NA
## [1] TRUE TRUE TRUE TRUE TRUE FALSE FALSE FALSE FALSE FALSE
## [1] TRUE TRUE TRUE TRUE TRUE FALSE FALSE FALSE FALSE FALSE
あんましわかっていないけど, これらの関数を使うとaggregateが明示的に行えるようになる. これを使えば,・・・?ポスグレなどとの対応がわかりやすくなる.
n = 24L
set.seed(25)
DT <- data.table(
color = sample(c("green","yellow","red"), n, TRUE),
year = as.Date(sample(paste0(2011:2015,"-01-01"), n, TRUE)),
status = as.factor(sample(c("removed","active","inactive","archived"), n, TRUE)),
amount = sample(1:5, n, TRUE),
value = sample(c(3, 3.5, 2.5, 2), n, TRUE)
)
DT[1:10]dt = data.table(
x = 1:10,
y = rep(c("a", "b"), each = 5)
)
cube(dt, .(n = .N), by = "y", id = TRUE)groupingsets(
DT,
j = c(list(count=.N), lapply(.SD, sum)),
by = c("color","year","status"),
# setsはidをまとめるもので,今回の場合にはyear, statusがニコイチになる
sets = list("color", c("year","status"), character()),
id=TRUE)高速な文字列マッチング. らしいけど,この例ではそこまで差がないもよう.
N = 1e6
# N is set small here (1e5) to reduce runtime because every day CRAN runs and checks
# all documentation examples in addition to the package's test suite.
# The comments here apply when N has been changed to 1e8 and were run on 2018-05-13
# with R 3.5.0 and data.table 1.11.2.
u = as.character(as.hexmode(1:10000))
y = sample(u,N,replace=TRUE)
x = sample(u)
# With N=1e8 ...
system.time(a <- match(x,y)) # 4.6s## user system elapsed
## 0.03 0.00 0.03
## user system elapsed
## 0.02 0.00 0.02
## [1] TRUE
## user system elapsed
## 0.03 0.00 0.03
## user system elapsed
## 0.01 0.00 0.01
## [1] TRUE
# Different example with more unique strings ...
u = as.character(as.hexmode(1:(N/10)))
y = sample(u,N,replace=TRUE)
x = sample(u,N,replace=TRUE)
system.time(a <- match(x,y)) # 46s## user system elapsed
## 0.11 0.00 0.11
## user system elapsed
## 0.04 0.00 0.05
## [1] TRUE